package cn.jack.h264encodeanddecode

import android.media.MediaCodec
import android.media.MediaFormat
import android.view.Surface
import java.io.ByteArrayOutputStream
import java.io.DataInputStream
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import java.io.InputStream

class PlayH264Helper(
    private val path: String,
    surface: Surface?,
    width: Int,
    height: Int
) : Runnable {
    private lateinit var mMediaCodec: MediaCodec

    init {
        try {
            //2.创建MediaCodec
            mMediaCodec = MediaCodec.createDecoderByType("video/avc")
            //3.1.mediaformat:用于指定媒体格式，包括音视频的编码格式、采样率、帧率等信息。它描述了输入数据的格式，以便编解码器能够正确地处理数据。
            val mediaformat = MediaFormat.createVideoFormat("video/avc", width, height)
            mediaformat.setInteger(MediaFormat.KEY_FRAME_RATE, 15)
            //3.配置编解码器的参数
            //3.2.参数2：该参数用于指定一个Surface对象，如果你想在解码过程中将解码后的视频帧渲染到屏幕上，
            // 可以通过这个Surface进行渲染。如果你只是进行音频解码，可以将此参数设置为null。
            //3.3.参数3：如果你需要对音视频进行数字版权管理（DRM）或其他加密操作，可以通过这个参数传递相应的MediaCrypto对象。如果不需要加密，可以将此参数设置为null。
            //3.4.参数4：将 flags 设置为 0 表明不启用任何附加的特性或标志，仅使用默认的配置。
            //通过设置 CONFIGURE_FLAG_ENCODE 来表示这是一个编码器，或者设置 CONFIGURE_FLAG_SECURE 来表示需要安全的解码。但如果没有这些特殊需求，通常可以将 flags 参数保持为 0。
            mMediaCodec.configure(mediaformat, surface, null, 0)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    //4.解码器开始工作
    fun play() {
        mMediaCodec.start()
        Thread(this).start()
    }

    override fun run() {
        try {
            //5.解码H264
            decodeH264()
        } catch (e: Exception) {
            println("解码失败: $e")
        }
    }

    private fun decodeH264() {
        var bytes: ByteArray? = null
        try {
            //5.1.非规范写法，要是解码的数据很大，需要分批次的读入,这里采取一次性写入
            bytes = getBytes(path)
        } catch (e: Exception) {
            e.printStackTrace()
        }
        //6.获取所有的ByteBuffer    暂时使用该api
        val inputBuffers = mMediaCodec.inputBuffers

        //9.定义起始码的开始索引
        var startIndex = 0
        assert(bytes != null)
        val totalSize = bytes!!.size
        while (true) {
            //9.2.边界值处理
            if (totalSize == 0 || startIndex >= totalSize) {
                break
            }

            //9.1.定义起始码的结束索引（并寻找）
            //参数2 额外+2（不一定要非+2不可，可以加）的目的 跳过sps 和 pps
            val nextFrameStart = findByFrame(bytes, startIndex + 2, totalSize)
            val info = MediaCodec.BufferInfo()

            //============查询哪一个bytebuffer能够用============

            //7.如果在指定的时间没有查询出可用的ByteBuffer，就返回-1
            val inIndex = mMediaCodec.dequeueInputBuffer(10000)
            startIndex = if (inIndex >= 0) {
                //8.根据索引找到对应的ByteBuffer
                val byteBuffer = inputBuffers[inIndex]
                //8.1.执行清空的操作
                byteBuffer.clear()

                //10.将bytes中指定长度的内容放入到byteBuffer中
                byteBuffer.put(bytes, startIndex, nextFrameStart - startIndex)
                //11.通知dsp芯片进行解码
                //参数1：要放入的输入缓冲区的索引
                //参数2：输入数据在输入缓冲区中的偏移量。通常，可以设置为 0，表示从输入数据的起始位置开始。
                //参数3：表示输入数据的大小，即要放入输入缓冲区的数据的字节数
                //参数4：表示输入数据的展示时间戳，以微秒为单位。对于视频来说，它通常是视频帧的显示时间；对于音频来说，它通常是音频样本的播放时间。
                //可以通过调整时间戳来实现对播放速度的控制
                //参数5：表示输入数据的标志位，用于指定一些特殊的处理要求。常见的标志位包括：
                //BUFFER_FLAG_KEY_FRAME: 表示关键帧。
                //BUFFER_FLAG_END_OF_STREAM: 表示输入流结束。
                //传入0，表示没有特殊的标志或配置，即不启用任何额外的特性。
                mMediaCodec.queueInputBuffer(inIndex, 0, nextFrameStart - startIndex, 0, 0)
                nextFrameStart
            } else {
                //12.dsp芯片处于忙于状态，等待
                continue
            }

            //13.（从输出缓冲区队列中获取可用的输出缓冲区索引）获取解码后的数据，以便应用程序可以进一步处理或渲染。
            //13.1.在参数2时间内没有获取到就返回-1     dequeueOutputBuffer方法的设计有些不太一样
            //13.2.参数1：一个 MediaCodec.BufferInfo 对象，用于存储输出缓冲区的相关信息，例如数据的大小、时间戳等。
            val outIndex = mMediaCodec.dequeueOutputBuffer(info, 10000)
            if (outIndex >= 0) {
                try {
                    //15.休眠一下，控制播放速度
                    Thread.sleep(33)
                } catch (e: InterruptedException) {
                    e.printStackTrace()
                }
                //14.解码器的输出缓冲区中包含解码后的音视频数据。应用程序可以根据具体的需求，将这些数据用于播放、存储或其他后续处理。在处理完数据后，
                // 需要调用 releaseOutputBuffer 方法来释放输出缓冲区，使其变为可用状态供下一次使用。
                //14.1.render 参数表示是否渲染该输出缓冲区的数据。如果设置为 true，表示渲染；如果设置为 false，则不进行渲染。
                mMediaCodec.releaseOutputBuffer(outIndex, true)
            } else {
                println("获取解码后的数据失败 ")
            }
        }
    }

    private fun findByFrame(bytes: ByteArray?, start: Int, totalSize: Int): Int {
        for (i in start until totalSize - 4) {
            if (bytes!![i].toInt() == 0x00 && bytes[i + 1].toInt() == 0x00 && bytes[i + 2].toInt() == 0x00 && bytes[i + 3].toInt() == 0x01 || bytes[i].toInt() == 0x00 && bytes[i + 1].toInt() == 0x00 && bytes[i + 2].toInt() == 0x01) {
                return i
            }
        }
        return -1
    }

    @Throws(IOException::class)
    fun getBytes(path: String): ByteArray {
        val `is`: InputStream = DataInputStream(FileInputStream(File(path)))
        var len: Int
        val size = 1024
        var buf: ByteArray
        val bos = ByteArrayOutputStream()
        buf = ByteArray(size)
        while (`is`.read(buf, 0, size).also { len = it } != -1) bos.write(buf, 0, len)
        buf = bos.toByteArray()
        return buf
    }
}
